/* ui_curses.c -- simple interactive curses UI
**
** Copyright 2006, Brian Swetland.  All rights reserved.
** See provided LICENSE file or http://frotz.net/LICENSE for details.
*/

#include <ctype.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>

#include <curses.h>
#include <signal.h>
#include <sys/select.h>

#include "debugger.h"

static int RESIZE = 0;
static int REPAINT = 0;
static int EXIT = 0;

static void _interrupt(int s)
{
    EXIT = 1;
    endwin();
    exit(0);
}

static void _resize(int s)
{
    RESIZE = 1;
}

static void curses_init()
{
    signal(SIGINT, _interrupt);
    signal(SIGWINCH, _resize);
    initscr();
    keypad(stdscr, 1);
    nonl();
    cbreak();
    noecho();   
}

typedef struct line line;

struct line
{
    line *prev, *next;
    char text[1];
};

static line _firstline = {
    .prev = &_firstline,
    .next = &_firstline,
    .text = { 0 },
};

static line _history = {
    .prev = &_history,
    .next = &_history,
    .text = { 0 },
};

static line *history = &_history;
static line *curhistory = 0;

static line *firstline = &_firstline;
static line *lastline = &_firstline;

static int status_lineno = 0;
static int input_lineno = 0;
static int width = 0;
static int height = 0;

void ui_addline(const char *str)
{
    int len = strlen(str) + 1;
    line *l = (line*) malloc(sizeof(line) + len);
    memcpy(l->text, str, len);
    l->next = firstline;
    l->prev = firstline->prev;
    firstline->prev->next = l;
    firstline->prev = l;

    if((lastline == firstline) || (lastline == l->prev)) {
        lastline = l;
    }
    REPAINT = 1;
}

static void addhistory(const char *str)
{
    const char *x;
    curhistory = 0;
    for(x = str; *x; x++){
        if(!isspace(*x)){
            int len = strlen(str) + 1;
            line *l = (line*) malloc(sizeof(line) + len);
            memcpy(l->text, str, len);
            l->next = history;
            l->prev = history->prev;
            history->prev->next = l;
            history->prev = l;
            return;
        }
    }
}


static char *BAR = "==============================================================================================================================================================";
static char entry[128];
static int entry_count = 0;

static void input_repaint()
{
    move(input_lineno, 0);
    clrtoeol();
    mvaddnstr(input_lineno, 0, entry, width);
}

static void display_repaint()
{
    line *l;
    int n;
    
    erase();

    l = lastline;
    for(n = status_lineno - 1; n >= 0; n--){
        if(l == firstline) break;
        mvaddnstr(n, 0, l->text, width);
        l = l->prev;
    }
    mvaddnstr(status_lineno, 0, BAR, width);
    mvaddnstr(input_lineno, 0, entry, width);

    REPAINT = 0;
}

static void display_init()
{
    int h, w;
    getmaxyx(stdscr, h, w);

    if((h < 10) || (w < 40)){
        endwin();
        printf("display too small...\n");
        exit(0);
    }

    width = w;
    height = h;
    
    erase();

    input_lineno = h - 1;
    status_lineno = h - 2;
}

#define MAX_ARGS 8

void process_command(char *cmd);

#define CTRL(x) ((x) - 'a' + 1)
static void process_key(int key)
{
    int n;
    
    if((key >= ' ') && (key < 127)){
        entry[entry_count++] = key;
        entry[entry_count] = 0;
        return;
    }

    switch(key){
    case KEY_BACKSPACE:
        if(entry_count > 0){
            entry_count--;
            entry[entry_count] = 0;
        }
        break;
        
    case KEY_ENTER:
    case 13:
        if(entry_count == 0) break;
        if(!strcmp(entry,"quit")) {
            endwin();
            exit(0);
        }
        addhistory(entry);
        process_command(entry);
        entry_count = 0;
        entry[0] = 0;
        break;

    case KEY_LEFT:
        break;
        
    case KEY_RIGHT:
        break;
        
    case KEY_UP:
        if(curhistory) {
            if(curhistory->prev == history) {
                return;
            } else {
                curhistory = curhistory->prev;
            }
        } else {
            if(history->prev == history) {
                return;
            } else {
                curhistory = history->prev;
            }
        }
        
        strcpy(entry, curhistory->text);
        entry_count = strlen(entry);
        break;
        
    case KEY_DOWN:
        if(curhistory == 0) return;
        if(curhistory->next == history) {
            curhistory = 0;
            entry_count = 0;
            entry[0] = 0;
            return;
        }
        curhistory = curhistory->next;
        strcpy(entry, curhistory->text);
        entry_count = strlen(entry);
        break;

    case KEY_PPAGE:
    case CTRL('p'):
        n = status_lineno - 1;
        while((n-- > 0) && (lastline->prev != firstline)) {
            lastline = lastline->prev;
        }
        REPAINT = 1;
        break;

    case KEY_NPAGE:
    case CTRL('n'):
        n = status_lineno - 1;
        while((n-- > 0) && (lastline->next != firstline)) {
            lastline = lastline->next;
        }
        REPAINT = 1;
        break;
    }
}

void ui_clear()
{}

void ui_start()
{
    struct timeval tv;
    fd_set fds;
    int r;
    
    REPAINT = 1;
    
    curses_init();
    display_init();
    refresh();
    
    FD_ZERO(&fds);
    
    for(;;) {
        if(EXIT) break;
        
        if(REPAINT) {
            display_repaint();
            refresh();
            REPAINT = 0;
        }
        
        FD_SET(0, &fds);
        if(DEBUGIO->poll != 0){
            tv.tv_sec = 0;
            tv.tv_usec = 100 * 1000;
            r = select(1, &fds, 0, 0, &tv);
            if(r == 0) {
                DEBUGIO->poll(DEBUGIO);
                continue;
            }
        } else {
            select(1, &fds, 0, 0, 0);
        }
        if(RESIZE) {
            endwin();
            refresh();
            display_init();
            refresh();
            RESIZE = 0;
            REPAINT = 1;
            
            continue;
        }

        if(FD_ISSET(0, &fds)){
            process_key(getch());
            input_repaint();
            refresh();
        }
    }
    endwin();
}

